/**
* @file catlog.c in catlog
* @author linfe
* @comment
* Create on 2023/3/13 23:10
* @version 1.0
**/


#include "catlog.h"
#include "catlog_port.h"

#include <stdarg.h>
#include <malloc.h>
#include <stdio.h>

#if CATLOG_USE_XPRITF == 0
#define catlog_sprintf sprintf
#else
#include "xprintf.h"
extern char *strptr;
#define catlog_sprintf xsprintf
#endif


#if CATLOG_BUFFER_STATIC == 1
char catlog_buffer[CATLOG_BUFFER_SIZE];
#else
static uint8_t *catlog_buffer = NULL;
#endif
static uint32_t catlog_offset = 0;

uint32_t catlog_push_out_cnts = 0;

#if CATLOG_MUTEX_LOCK_ENABLE == 1
catlog_mutex_t mutex;
#endif


void catlog_push_out() {
#if CATLOG_OUTPUT_MODE == CATLOG_OUTPUT_TO_STDERR
    catlog_write_to_stderr(catlog_buffer, catlog_offset);
#elif  CATLOG_OUTPUT_MODE == CATLOG_OUTPUT_TO_FILE
    catlog_write_to_file(catlog_buffer,catlog_offset);
#elif CATLOG_OUTPUT_MODE == CATLOG_OUTPUT_TO_DEVICE
    catlog_write_to_device(catlog_buffer, catlog_offset);
#endif
    catlog_buffer[0] = '\0';
    catlog_offset = 0;
    catlog_push_out_cnts++;
}

void catlog_init() {
#if CATLOG_PUSHOUT_LEVEL != CATLOG_DISABLE

#if CATLOG_BUFFER_STATIC != 1
    catlog_buffer = catlog_malloc(sizeof(char) * CATLOG_BUFFER_SIZE);
#endif
    catlog_offset = 0;
#if CATLOG_OUTPUT_MODE == CATLOG_OUTPUT_TO_STDERR
    catlog_open_stderr(NULL);
#elif  CATLOG_OUTPUT_MODE == CATLOG_OUTPUT_TO_FILE
    char filename[25];
    time_t cur = time(NULL);
    struct tm *t = localtime(&cur);
    sprintf(filename,"%04d%02d%02d_%02d%02d%02d.log",
            1900 + t->tm_year,
            t->tm_mon,
            t->tm_mday,
            t->tm_hour,
            t->tm_min,
            t->tm_sec
            );
    catlog_open_file(filename);
#elif CATLOG_OUTPUT_MODE == CATLOG_OUTPUT_TO_DEVICE
    catlog_open_device(NULL);
#endif

#if CATLOG_MUTEX_LOCK_ENABLE == 1
    catlog_mutex_init(&mutex);
#endif

#endif
}

#include "xprintf.h"

#define CATLOG_XPRINTF_MSG_2_BUFF(fmt)                                                  \
{                                                                                       \
    va_list arp;                                                                        \
strptr = &(catlog_buffer[catlog_offset]);        /* Enable destination for memory */    \
va_start(arp, fmt);                                                                     \
xvfprintf(0, fmt, arp);                                                                 \
va_end(arp);                                                                            \
*strptr = 0;        /* Terminate output string */                                       \
int res = strptr - &(catlog_buffer[catlog_offset]);                                     \
strptr = NULL;            /* Disable destination for memory */                            \
catlog_offset += res;                                                                   \
}


#define CATLOG_SYS_PRINTF_MSG_2_BUFF(fmt)   \
{                                  \
    va_list ap;                         \
int retval;                         \
va_start(ap, fmt);                  \
retval = vsprintf((char *) catlog_buffer + catlog_offset, fmt, ap); \
va_end(ap); \
catlog_offset += retval;           \
                                   \
}

static uint32_t add_time_stamp() {
    uint32_t res = catlog_get_sys_time_str(catlog_buffer + catlog_offset);
    catlog_offset += res;
    return 0;
}

static uint32_t add_head_mark(catlog_level_t level) {
    int res = 0;
    switch (level) {
        case CATLOG_INFO:
            /*\033[0m*/
            res = sprintf((char *) catlog_buffer + catlog_offset, "\033[0m");
            break;
        case CATLOG_DEBUG:
            res = sprintf((char *) catlog_buffer + catlog_offset, "\033[0;32m");
            break;
        case CATLOG_WARNING:
            res = sprintf((char *) catlog_buffer + catlog_offset, "\033[0;33m");
            break;
        case CATLOG_ERROR:
        case CATLOG_FETAL:
            res = sprintf((char *) catlog_buffer + catlog_offset, "\033[0;31m");
            break;
        default:
            break;
    }
    catlog_offset += res;
    return 0;
}

static uint32_t add_end_mark(catlog_level_t level) {
    int res = 0;
    res = sprintf((char *) catlog_buffer + catlog_offset, "\033[0m");
    catlog_offset += res;
    return 0;
}

static uint32_t add_endline() {
#ifndef WIN32
    catlog_buffer[catlog_offset] = '\r';
    catlog_buffer[catlog_offset + 1] = '\n';
    catlog_offset += 2;
#else
    catlog_buffer[catlog_offset] = '\n';
    catlog_offset += 1;
#endif
    return 0;
}

static uint32_t add_level(catlog_level_t level) {
    static char *level_table[5] = {
            "<I>", "<D>", "<W>", "<E>", "<F>"
    };
    uint32_t res = catlog_sprintf((char *) catlog_buffer + catlog_offset, "%s ", level_table[level]);
    catlog_offset += res;
    return 0;
}

static uint32_t add_function_name(const char *name) {
    uint32_t res = catlog_sprintf((char *) catlog_buffer + catlog_offset, "%s: ", name);
    catlog_offset += res;
    return res;
}

void log_impl(catlog_level_t level, const char *func_name, const char *fmt, ...) {
#if CATLOG_MUTEX_LOCK_ENABLE == 1
    catlog_mutex_lock(&mutex);
#endif
#if CATLOG_ANSI_SUPPORT == 1
    add_head_mark(level);
#endif

    add_time_stamp();
    add_level(level);

#if CATLOG_ADD_FUNCTION_NAME == 1
    add_function_name(func_name);
#endif

#if CATLOG_USE_XPRITF
    CATLOG_XPRINTF_MSG_2_BUFF(fmt);
#else
    CATLOG_SYS_PRINTF_MSG_2_BUFF(fmt)
#endif
#if CATLOG_ANSI_SUPPORT == 1
    add_end_mark(level);
#endif
    add_endline();
#if CATLOG_CACHED_SUPPORT == 1
    if (CATLOG_BUFFER_SIZE - catlog_offset < CATLOG_MAX_CHARS_ONE_LINE) {
        catlog_push_out();
    }
#else
    catlog_buffer[catlog_offset++] = '\0';
    catlog_push_out();
#endif
#if CATLOG_MUTEX_LOCK_ENABLE == 1
    catlog_mutex_unlock(&mutex);
#endif
}

void catlog_deinit() {
#if CATLOG_PUSHOUT_LEVEL != CATLOG_DISABLE
    if (catlog_offset > 0) {
        catlog_push_out();
    }
#if CATLOG_OUTPUT_MODE == CATLOG_OUTPUT_TO_STDERR
    catlog_close_stderr(NULL);
#elif  CATLOG_OUTPUT_MODE == CATLOG_OUTPUT_TO_FILE
    catlog_close_file(NULL);
#elif CATLOG_OUTPUT_MODE == CATLOG_OUTPUT_TO_DEVICE
    catlog_close_device(NULL);
#endif
#if !CATLOG_BUFFER_STATIC
    free(catlog_buffer);
    catlog_buffer = NULL;
#endif
#endif
}